

#include "PlatformConfig.h"

///// include the kazMath library
//#include "Common/CCVectors.h"
//#include "Common/CCMatrixs.h"

#include "CCMeshCube.h"

USING_NAMESPACE_BECO

// Expressed as float so gluPerspective division returns a float and not 0 (640/480 != 640.0/480.0).
static Real RENDER_WIDTH = 640.0;
static Real RENDER_HEIGHT = 480.0;
static Real SHADOW_MAP_RATIO = 2.0;

//Camera position
Real p_camera[3] = {32,20,0};

//Camera lookAt
Real l_camera[3] = {2,0,-10};

//Light position
Real p_light[3] = {3,20,0};

//Light lookAt
Real l_light[3] = {0,0,-5};


//Light mouvement circle radius
Real light_mvnt = 30.0;

// Hold id of the framebuffer for light POV rendering
GLuint fboId;

// Z values will be rendered to this texture when using fboId framebuffer
GLuint depthTextureId;

// Use to activate/disable shadowShader
GLhandleARB shadowShaderId;
GLuint shadowMapUniform;

static Mesh* gMeshObject = NULL;

GLFWwindow* pGLFWWindow = 0;;

// Loading shader function
GLhandleARB loadShader(const char* filename, unsigned int type)
{
	FILE *pfile;
	GLhandleARB handle;
	const GLcharARB* files[1];
	
	// shader Compilation variable
	Int32 result;				// Compilation code result
	Int32 errorLoglength ;
	char* errorLogText;
	GLsizei actualErrorLogLength;
	
	char buffer[400000];
	memset(buffer,0,400000);
	
	// This will raise a warning on MS compiler
	pfile = fopen(filename, "rb");
	if(!pfile)
	{
		printf("Sorry, can't open file: '%s'.\n", filename);
		exit(0);
	}
	
	fread(buffer,sizeof(char),400000,pfile);
	//printf("%s\n",buffer);
	
	
	fclose(pfile);
	
	handle = glCreateShaderObjectARB(type);
	if (!handle)
	{
		//We have failed creating the vertex shader object.
		printf("Failed creating vertex shader object from file: %s.",filename);
		exit(0);
	}
	
	files[0] = (const GLcharARB*)buffer;
	glShaderSourceARB(handle, //The handle to our shader
					  1, //The number of files.
					  files, //An array of const char * data, which represents the source code of theshaders
					  NULL);
	
	glCompileShaderARB(handle);
	
	//Compilation checking.
	glGetObjectParameterivARB(handle, GL_OBJECT_COMPILE_STATUS_ARB, &result);
	
	// If an error was detected.
	if (!result)
	{
		//We failed to compile.
		printf("Shader '%s' failed compilation.\n",filename);
		
		//Attempt to get the length of our error log.
		glGetObjectParameterivARB(handle, GL_OBJECT_INFO_LOG_LENGTH_ARB, &errorLoglength);
		
		//Create a buffer to read compilation error message
		errorLogText = (char*)malloc(sizeof(char) * errorLoglength);
		
		//Used to get the final length of the log.
		glGetInfoLogARB(handle, errorLoglength, &actualErrorLogLength, errorLogText);
		
		// Display errors.
		printf("%s\n",errorLogText);
		
		// Free the buffer malloced earlier
		free(errorLogText);
	}
	
	return handle;
}

void loadShadowShader()
{
	GLhandleARB vertexShaderHandle;
	GLhandleARB fragmentShaderHandle;
	
	vertexShaderHandle   = loadShader("VertexShader.glsl",GL_VERTEX_SHADER);
	fragmentShaderHandle = loadShader("FragmentShader.glsl",GL_FRAGMENT_SHADER);
	
	shadowShaderId = glCreateProgramObjectARB();
	
	glAttachObjectARB(shadowShaderId,vertexShaderHandle);
	glAttachObjectARB(shadowShaderId,fragmentShaderHandle);
	glLinkProgramARB(shadowShaderId);
	
	shadowMapUniform = glGetUniformLocationARB(shadowShaderId,"ShadowMap");
}

void generateShadowFBO()
{
	const Int32 shadowMapWidth = RENDER_WIDTH * SHADOW_MAP_RATIO;
	const Int32 shadowMapHeight = RENDER_HEIGHT * SHADOW_MAP_RATIO;
	
	//Real borderColor[4] = {0,0,0,0};
	GLenum FBOstatus;
	
	// Try to use a texture depth component
	glGenTextures(1, &depthTextureId);
	glBindTexture(GL_TEXTURE_2D, depthTextureId);
	
	// GL_LINEAR does not make sense for depth texture. However, next tutorial shows usage of GL_LINEAR and PCF
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	
	// Remove artefact on the edges of the shadowmap
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
	
	//glTexParameterfv( GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor );
	
	// No need to force GL_DEPTH_COMPONENT24, drivers usually give you the max precision if available 
	glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, shadowMapWidth, shadowMapHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);
	glBindTexture(GL_TEXTURE_2D, 0);
	
	// create a framebuffer object
	glGenFramebuffersEXT(1, &fboId);
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId);
	
	// Instruct openGL that we won't bind a color texture with the currently binded FBO
	glDrawBuffer(GL_NONE);
	glReadBuffer(GL_NONE);
	
	// attach the texture to FBO depth attachment point
	glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,GL_TEXTURE_2D, depthTextureId, 0);
	
	// check FBO status
	FBOstatus = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
	if(FBOstatus != GL_FRAMEBUFFER_COMPLETE_EXT)
		printf("GL_FRAMEBUFFER_COMPLETE_EXT failed, CANNOT use FBO\n");
	
	// switch back to window-system-provided framebuffer
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
    
    return;
}

void setupPerspective(const Real angle, const Real kAspectRatio, const Real kNear, const Real kFar)
{
    const Real vFov = angle * glm::pi<Real>() / 180.0;

    glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
    
    glm::mat4 perspectiveMat = glm::perspective(vFov, kAspectRatio, kNear, kFar);
    glLoadMatrixf(glm::value_ptr(perspectiveMat));
    
    return;
}

void setupCameraLookAt(const Real eyeX, const Real eyeY, const Real eyeZ,
                       const Real centerX, const Real centerY, const Real centerZ,
                       const Real upX, const Real upY, const Real upZ)
{
    glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
    
//// method A
//    GLfloat directMat[16];
//    
//    ::memset((void*)directMat, 0, sizeof(GLfloat) * 16);
//    
//    directMat[15]= 1.0;
//    
//    Vector3f fvDirect(centerX-eyeX,centerY-eyeY,centerZ-eyeZ);
//    fvDirect.Normalize();
//    
//    Vector3f fvUpD(upX,upY,upZ);
//    fvUpD.Normalize();
//    
//    Vector3f fvC = Vector3f::CrossProduct(fvDirect, fvUpD);
//    fvC.Normalize();
//    
//    Vector3f fvUp = Vector3f::CrossProduct(fvC, fvDirect);
//    fvUp.Normalize();
//    
//    directMat[0] = fvC.x;
//    directMat[4] = fvC.y;
//    directMat[8] = fvC.z;
//    directMat[1] = fvUp.x;
//    directMat[5] = fvUp.y;
//    directMat[9] = fvUp.z;
//    directMat[2] = -fvDirect.x;
//    directMat[6] = -fvDirect.y;
//    directMat[10] = -fvDirect.z;
//
//    Matrix4f mat(directMat);
//    glLoadMatrixf(mat.get());
//    glTranslatef(-eyeX,-eyeY,-eyeZ);
    
    /// method B
    glm::mat4 projectionMat = glm::lookAt(glm::vec3(eyeX, eyeY, eyeZ), glm::vec3(centerX, centerY, centerZ), glm::vec3(upX, upY, upZ));
    glLoadMatrixf(glm::value_ptr(projectionMat));
    
    return;
}

void setupMatrices(float position_x,float position_y,float position_z,float lookAt_x,float lookAt_y,float lookAt_z)
{
	setupPerspective(45.0, RENDER_WIDTH / RENDER_HEIGHT, 10.0, 40000.0);
    setupCameraLookAt(position_x, position_y, position_z, lookAt_x, lookAt_y, lookAt_z, 0.0, 1.0, 0.0);

	return;
}

void resizeCallback(GLFWwindow* pWindows, int width, int height) {
	RENDER_WIDTH = width;
	RENDER_HEIGHT = height;
	return;
}


// This update only change the position of the light.
//int elapsedTimeCounter = 0;
void update(void)
{
	Real __timeElapse = glfwGetTime();
	p_light[0] = light_mvnt * cos(__timeElapse);
	p_light[2] = light_mvnt * sin(__timeElapse);

    return;
}


void setTextureMatrix(void)
{
	static GLdouble modelView[16];
	static GLdouble projection[16];
	
	// This is matrix transform every coordinate x,y,z
	// x = x* 0.5 + 0.5 
	// y = y* 0.5 + 0.5 
	// z = z* 0.5 + 0.5 
	// Moving from unit cube [-1,1] to [0,1]  
	const GLdouble bias[16] = {
		0.5, 0.0, 0.0, 0.0,
		0.0, 0.5, 0.0, 0.0,
		0.0, 0.0, 0.5, 0.0,
        0.5, 0.5, 0.5, 1.0
    };
	
	// Grab modelview and transformation matrices
	glGetDoublev(GL_MODELVIEW_MATRIX, modelView);
	glGetDoublev(GL_PROJECTION_MATRIX, projection);
	
	
	glMatrixMode(GL_TEXTURE);
	glActiveTextureARB(GL_TEXTURE7);
	
	glLoadIdentity();	
	glLoadMatrixd(bias);
	
	// concatating all matrice into one.
	glMultMatrixd(projection);
	glMultMatrixd(modelView);
	
	return;
}

// During translation, we also have to maintain the GL_TEXTURE8, used in the shadow shader
// to determine if a vertex is in the shadow.
void startTranslate(Real x,Real y,Real z)
{
	glPushMatrix();
	glTranslatef(x,y,z);
	
	glMatrixMode(GL_TEXTURE);
	glActiveTextureARB(GL_TEXTURE7);
	glPushMatrix();
	glTranslatef(x,y,z);
}

void endTranslate()
{
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
}

void drawObjects(void)
{
	// Ground
	glColor4f(0.3f, 0.3f, 0.3f, 1);
	glBegin(GL_QUADS);
	glVertex3f(-35, 2, -35);
	glVertex3f(-35, 2, 15);
	glVertex3f(15, 2, 15);
	glVertex3f(15, 2, -35);
	glEnd();
	
	glColor4f(0.9f,0.9f,0.9f,1);
	
	// Instead of calling glTranslatef, we need a custom function that also maintain the light matrix
	startTranslate(0,4,-16);
	gMeshObject->Render(0);	//glutSolidCube(4);
	endTranslate();
	
	startTranslate(0,4,-5);
	gMeshObject->Render(0);	//glutSolidCube(4);
	endTranslate();

	
	
	return;
}

void renderScene(void) 
{
	//First step: Render from the light POV to a FBO, story depth values only
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,fboId);	//Rendering offscreen
	
	//Using the fixed pipeline to render to the depthbuffer
	glUseProgramObjectARB(0);
	
	// In the case we render the shadowmap to a higher resolution, the viewport must be modified accordingly.
	glViewport(0,0,RENDER_WIDTH * SHADOW_MAP_RATIO,RENDER_HEIGHT* SHADOW_MAP_RATIO);
	
	// Clear previous frame values
	glClear( GL_DEPTH_BUFFER_BIT);
	
	//Disable color rendering, we only want to write to the Z-Buffer
	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 
	
	setupMatrices(p_light[0],p_light[1],p_light[2],l_light[0],l_light[1],l_light[2]);
	
	// Culling switching, rendering only backface, this is done to avoid self-shadowing
	glCullFace(GL_FRONT);
	drawObjects();
	
	//Save modelview/projection matrice into texture7, also add a biais
	setTextureMatrix();
	
	// Now rendering from the camera POV, using the FBO to generate shadows
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,0);
	
	glViewport(0,0,RENDER_WIDTH,RENDER_HEIGHT);
	
	//Enabling color write (previously disabled for light POV z-buffer rendering)
	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 
	
	// Clear previous frame values
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	//Using the shadow shader
	glUseProgramObjectARB(shadowShaderId);
	glUniform1iARB(shadowMapUniform,7);
	glActiveTextureARB(GL_TEXTURE7);
	glBindTexture(GL_TEXTURE_2D,depthTextureId);

	setupMatrices(p_camera[0],p_camera[1],p_camera[2],l_camera[0],l_camera[1],l_camera[2]);
	
	glCullFace(GL_BACK);
	drawObjects();
	
    
#if 0
	// DEBUG only. this piece of code draw the depth buffer onscreen
    glUseProgramObjectARB(0);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-RENDER_WIDTH/2,RENDER_WIDTH/2,-RENDER_HEIGHT/2,RENDER_HEIGHT/2,1,20);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glColor4f(1,1,1,1);
    glActiveTextureARB(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D,depthTextureId);
    glEnable(GL_TEXTURE_2D);
    glTranslated(0,0,-1);
    glBegin(GL_QUADS);
    glTexCoord2d(0,0);glVertex3f(0,0,0);
    glTexCoord2d(1,0);glVertex3f(RENDER_WIDTH/2,0,0);
    glTexCoord2d(1,1);glVertex3f(RENDER_WIDTH/2,RENDER_HEIGHT/2,0);
    glTexCoord2d(0,1);glVertex3f(0,RENDER_HEIGHT/2,0);
    
    glEnd();
    glDisable(GL_TEXTURE_2D);
#endif  /// 0
    
	
	return;
}

void processNormalKeys(GLFWwindow* pWindows, int key, int scanCode, int action, int mods)
{
	if (action == GLFW_PRESS) {

	}
	else if (action == GLFW_RELEASE) {
		if (scanCode == 27 || key == GLFW_KEY_ESCAPE)
			exit(0);
	}

	return;
}

#if defined(PLATFORM_WIN32)
int WINAPI WinMain(
	HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPSTR lpCmdLine,
	int nCmdShow)

#elif defined(PLATFORM_MACOSX)
int main(int argc, char* argv[])

#endif	//
{
	do {
		if (!glfwInit()) {
			break;
		}

        
//        printf("%s, %s, %s, %s", glGetString(GL_VERSION), glGetString(GL_VENDOR), glGetString(GL_RENDER), glGetString(GL_SHADING_LANGUAGE_VERSION));
        
        glfwWindowHint(GLFW_SAMPLES, 4);
        glfwWindowHint(GLFW_VERSION_MAJOR, 2);
        glfwWindowHint(GLFW_VERSION_MINOR, 1);
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_ANY_PROFILE);

		if (!(pGLFWWindow = glfwCreateWindow(RENDER_WIDTH, RENDER_HEIGHT, "Shadow Mapping Demo", NULL, NULL))) {
			break;
		}
		glfwMakeContextCurrent(pGLFWWindow);

		/// initialize the GLEW library
		glewInit();
        
        printf("%s, %s, %s, %s", glGetString(GL_VERSION), glGetString(GL_VENDOR), glGetString(GL_RENDER), glGetString(GL_SHADING_LANGUAGE_VERSION));

		// This call will grab openGL extension function pointers.
		// This is not necessary for macosx and linux
#if defined(PLATFORM_WIN32) && !defined(__GLEW_H__)
		getOpenGLFunctionPointers();
#endif
		generateShadowFBO();
		loadShadowShader();

		/// load mesh object
		gMeshObject = new Cube(4.0, 4.0, 4.0);

		// This is important, if not here, FBO's depthbuffer won't be populated.
		glEnable(GL_DEPTH_TEST);
		glClearColor(0, 0, 0, 1.0f);

		glEnable(GL_CULL_FACE);
		glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

		glfwSetKeyCallback(pGLFWWindow, processNormalKeys);
		//glfwSetFramebufferSizeCallback(pGLFWWindow, resizeCallback);
		glfwSetWindowSizeCallback(pGLFWWindow, resizeCallback);

		while (!glfwWindowShouldClose(pGLFWWindow)) {
            update();
			renderScene();
            
            glfwSwapBuffers(pGLFWWindow);
            glfwPollEvents();
		}

	} while (false);

	if (pGLFWWindow) {
		glfwDestroyWindow(pGLFWWindow);
		pGLFWWindow = 0;
	}
    
    if (gMeshObject) {
        delete gMeshObject;
        gMeshObject = NULL;
    }

	glfwTerminate();
	return 0;
}
